/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard;

import java.util.ArrayList;
import java.util.List;

import proguard.classfile.ClassConstants;
import proguard.classfile.attribute.visitor.AttributeNameFilter;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.visitor.ClassPoolVisitor;
import proguard.classfile.visitor.ClassVisitor;
import proguard.classfile.visitor.MemberDescriptorReferencedClassVisitor;
import proguard.classfile.visitor.MemberVisitor;
import proguard.classfile.visitor.MultiClassPoolVisitor;
import proguard.classfile.visitor.MultiClassVisitor;
import proguard.classfile.visitor.MultiMemberVisitor;

/**
 * This factory creates visitors to efficiently travel to specified classes and class members.
 *
 * @author Eric Lafortune
 */
public class KeepClassSpecificationVisitorFactory extends ClassSpecificationVisitorFactory {
    private final boolean shrinking;
    private final boolean optimizing;
    private final boolean obfuscating;

    /**
     * Creates a new KeepClassSpecificationVisitorFactory that creates visitors for the specified goal.
     *
     * @param shrinking a flag that specifies whether the visitors are intended for the shrinking step.
     * @param optimizing a flag that specifies whether the visitors are intended for the optimization step.
     * @param obfuscating a flag that specifies whether the visitors are intended for the obfuscation step.
     */
    public KeepClassSpecificationVisitorFactory(boolean shrinking, boolean optimizing, boolean obfuscating) {
        this.shrinking = shrinking;
        this.optimizing = optimizing;
        this.obfuscating = obfuscating;
    }

    // Overriding implementations for ClassSpecificationVisitorFactory.

    /**
     * Constructs a ClassPoolVisitor to efficiently travel to the specified classes, class members and code attributes.
     *
     * @param keepClassSpecifications the specifications of the class(es) and class members to visit.
     * @param classVisitor an optional ClassVisitor to be applied to matching classes.
     * @param fieldVisitor an optional MemberVisitor to be applied to matching fields.
     * @param methodVisitor an optional MemberVisitor to be applied to matching methods.
     * @param attributeVisitor an optional AttributeVisitor to be applied to matching code attributes.
     */
    public ClassPoolVisitor createClassPoolVisitor(List keepClassSpecifications, ClassVisitor classVisitor,
        MemberVisitor fieldVisitor, MemberVisitor methodVisitor, AttributeVisitor attributeVisitor) {
        MultiClassPoolVisitor multiClassPoolVisitor = new MultiClassPoolVisitor();

        if (keepClassSpecifications != null) {
            for (int index = 0; index < keepClassSpecifications.size(); index++) {
                KeepClassSpecification keepClassSpecification =
                    (KeepClassSpecification)keepClassSpecifications.get(index);

                if ((shrinking && !keepClassSpecification.allowShrinking)
                    || (optimizing && !keepClassSpecification.allowOptimization)
                    || (obfuscating && !keepClassSpecification.allowObfuscation)) {
                    multiClassPoolVisitor.addClassPoolVisitor(createClassPoolVisitor(keepClassSpecification,
                        classVisitor, fieldVisitor, methodVisitor, attributeVisitor));
                }
            }
        }

        return multiClassPoolVisitor;
    }

    /**
     * Constructs a ClassPoolVisitor to efficiently travel to the specified classes, class members, and attributes.
     *
     * @param keepClassSpecification the specifications of the class(es) and class members to visit.
     * @param classVisitor an optional ClassVisitor to be applied to matching classes.
     * @param fieldVisitor an optional MemberVisitor to be applied to matching fields.
     * @param methodVisitor an optional MemberVisitor to be applied to matching methods.
     * @param attributeVisitor an optional AttributeVisitor to be applied to matching code attributes.
     */
    public ClassPoolVisitor createClassPoolVisitor(KeepClassSpecification keepClassSpecification,
        ClassVisitor classVisitor, MemberVisitor fieldVisitor, MemberVisitor methodVisitor,
        AttributeVisitor attributeVisitor) {
        // Start a global list of wilcdard matchers, so they can be referenced
        // from regular expressions. They are identified by their indices,
        // which imposes a number of tricky constraints:
        // - They need to be parsed in the right order, so the list is filled
        // out in the expected order (corresponding to the text file
        // configuration).
        // - They need to be matched in the right order, so the variable
        // matchers are matched before they are referenced.
        List variableStringMatchers = new ArrayList();

        // If specified, let the class visitor also visit the descriptor
        // classes and the signature classes.
        if (keepClassSpecification.markDescriptorClasses && classVisitor != null) {
            fieldVisitor = fieldVisitor == null ? new MemberDescriptorReferencedClassVisitor(classVisitor)
                : new MultiMemberVisitor(
                    new MemberVisitor[] {fieldVisitor, new MemberDescriptorReferencedClassVisitor(classVisitor)});

            methodVisitor = methodVisitor == null ? new MemberDescriptorReferencedClassVisitor(classVisitor)
                : new MultiMemberVisitor(
                    new MemberVisitor[] {methodVisitor, new MemberDescriptorReferencedClassVisitor(classVisitor)});
        }

        // Don't visit the classes if not specified.
        if (!keepClassSpecification.markClasses && !keepClassSpecification.markConditionally) {
            classVisitor = null;
        }

        // Don't visit the code attributes if not specified.
        attributeVisitor = keepClassSpecification.markCodeAttributes
            ? new AttributeNameFilter(ClassConstants.ATTR_Code, attributeVisitor) : null;

        ClassSpecification condition = keepClassSpecification.condition;
        if (condition != null) {
            // Parse the condition. We need to parse it before the actual keep
            // specification, to make sure the list of variable string matchers
            // is filled out in the right order.

            // Create a placeholder for the class pool visitor that
            // corresponds to the actual keep specification. Note that we
            // visit the entire class pool for each matched class.
            MultiClassPoolVisitor keepClassPoolVisitor = new MultiClassPoolVisitor();

            // Parse the condition.
            ClassPoolVisitor conditionalKeepClassPoolVisitor =
                createClassTester(condition, keepClassPoolVisitor, variableStringMatchers);

            // Parse the actual keep specification and add it to the
            // placeholder.
            keepClassPoolVisitor.addClassPoolVisitor(createClassPoolVisitor(keepClassSpecification, classVisitor,
                fieldVisitor, methodVisitor, attributeVisitor, variableStringMatchers));

            return conditionalKeepClassPoolVisitor;
        } else {
            // Just parse the actual keep specification.
            return createClassPoolVisitor(keepClassSpecification, classVisitor, fieldVisitor, methodVisitor,
                attributeVisitor, variableStringMatchers);
        }
    }

    /**
     * Constructs a ClassPoolVisitor to efficiently travel to the specified classes and class members.
     *
     * @param keepClassSpecification the specifications of the class(es) and class members to visit.
     * @param classVisitor an optional ClassVisitor to be applied to matching classes.
     * @param fieldVisitor an optional MemberVisitor to be applied to matching fields.
     * @param methodVisitor an optional MemberVisitor to be applied to matching methods.
     * @param attributeVisitor an optional AttributeVisitor to be applied to matching code attributes.
     * @param variableStringMatchers a mutable list of VariableStringMatcher instances that match the wildcards.
     */
    private ClassPoolVisitor createClassPoolVisitor(KeepClassSpecification keepClassSpecification,
        ClassVisitor classVisitor, MemberVisitor fieldVisitor, MemberVisitor methodVisitor,
        AttributeVisitor attributeVisitor, List variableStringMatchers) {
        // If specified, let the marker visit the class and its class
        // members conditionally.
        if (keepClassSpecification.markConditionally) {
            // Parse the condition. We need to parse it before the actual keep
            // specification, to make sure the list of variable string matchers
            // is filled out in the right order.

            // Create a placeholder for the class visitor that corresponds to
            // the actual keep specification.
            MultiClassVisitor keepClassVisitor = new MultiClassVisitor();

            // Parse the condition. Only add string matchers locally.
            ClassPoolVisitor conditionalKeepClassPoolVisitor =
                createClassTester(keepClassSpecification, keepClassVisitor, new ArrayList(variableStringMatchers));

            // Parse the actual keep specification and add it to the
            // placeholder.
            keepClassVisitor.addClassVisitor(createCombinedClassVisitor(keepClassSpecification.attributeNames,
                keepClassSpecification.fieldSpecifications, keepClassSpecification.methodSpecifications, classVisitor,
                fieldVisitor, methodVisitor, attributeVisitor, variableStringMatchers));

            return conditionalKeepClassPoolVisitor;
        } else {
            // Just parse the actual keep specification.
            return super.createClassPoolVisitor(keepClassSpecification, classVisitor, fieldVisitor, methodVisitor,
                attributeVisitor, variableStringMatchers);
        }
    }
}
